package models.helpers;

import models.entities.PictureEntity;
import models.exceptions.GetEntityException;
import org.apache.commons.codec.digest.DigestUtils;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.text.MessageFormat;

public class ImageHelper {

    public static void checkImageSize(int width, int height) throws GetEntityException {
        int maxWidth = 1024;
        int maxHeight = 768;
        if (width > maxWidth || height > maxHeight)
            throw new GetEntityException(MessageFormat.format("Cannot load image with size: {0}x{1}", width, height));
    }

    public BufferedImage getResizedImage(Image sourceImage,
                                         int outputWidth, int outputHeight,
                                         boolean preserveAlpha, boolean crop) throws IOException {
        int sourceWidth = sourceImage.getWidth(null);
        int sourceHeight = sourceImage.getHeight(null);
        double widthScale = (double) outputWidth / (double) sourceWidth;
        double heightScale = (double) outputHeight / (double) sourceHeight;
        double scale;
        if (crop)
            scale = Math.max(widthScale, heightScale);
        else
            scale = Math.min(widthScale, heightScale);
        int imageType = preserveAlpha ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;
        Graphics2D canvas = null;
        try {
            BufferedImage outputImage = new BufferedImage(outputWidth, outputHeight, imageType);
            canvas = outputImage.createGraphics();
            if (preserveAlpha) {
                canvas.setComposite(AlphaComposite.Src);
            }
            //set background
            canvas.setPaint(Color.white);
            canvas.fillRect(0, 0, outputWidth, outputHeight);
            int scaledWidth = (int) (sourceWidth * scale);
            int scaledHeight = (int) (sourceHeight * scale);
            Image scaledImage = sourceImage.getScaledInstance(scaledWidth, scaledHeight, Image.SCALE_SMOOTH);
            int positionLeft = (outputWidth - scaledWidth) / 2;
            int positionTop = (outputHeight - scaledHeight) / 2;

            canvas.drawImage(scaledImage, positionLeft, positionTop, scaledWidth, scaledHeight, null);
            return outputImage;
        } finally {
            if (canvas != null) {
                canvas.dispose();
            }
        }
    }

    public static String getOSIndependentFilePath(String filePath) {
        return filePath.replace("/", System.getProperty("file.separator")).replace("\\", System.getProperty("file.separator"));
    }

    public static InputStream getImage(String dataPath, models.shop.Image picture, int width, int height, String entityName, boolean crop) throws IOException {
        BufferedImage image = getCachedImage(dataPath, picture.filePath, picture.id, width, height, entityName, "jpg", crop);
        return convertToInputStream("jpeg", image);
    }

    @Deprecated
    public static InputStream getImage(String applicationPath, PictureEntity picture, int width, int height, String entityName, String type) throws IOException {
        BufferedImage image = getCachedImage(applicationPath, picture.filePath, picture.id, width, height, entityName, type, false);
        return convertToInputStream(type, image);
    }


    private static BufferedImage getCachedImage(String applicationPath, String filePath, int imageId, int width, int height, String entityName, String type, boolean crop) throws IOException {
        File cachePath = buildCachePath(applicationPath, imageId, width, height, entityName, type);
        if (cachePath.canRead()) {
            //Read from cache path
            return ImageIO.read(cachePath);
        }
        BufferedImage resizedImage = readImageFromFileAndResize(applicationPath, filePath, width, height, crop);
        cacheImage(cachePath, resizedImage, type);
        return resizedImage;
    }

    private static BufferedImage readImageFromFileAndResize(String dataPath, String filePath, int width, int height, boolean crop) throws IOException {
        filePath = getOSIndependentFilePath(filePath);
        BufferedImage imageFromDBPath = ImageIO.read(new File(String.format("%s%s", dataPath, filePath)));
        ImageHelper imgHelper = new ImageHelper();
        return imgHelper.getResizedImage(imageFromDBPath, width, height, true, crop);
    }

    private static void cacheImage(File cachePath, BufferedImage resizedImage, String type) throws IOException {
        if (!cachePath.exists()) {
            cachePath.createNewFile();
        }
        if (cachePath.canWrite()) {
            ImageIO.write(resizedImage, type, cachePath);
        }
    }

    private static File buildCachePath(String applicationPath, int imageId, int width, int height, String entityName, String type) throws IOException {
        String relativePath = MessageFormat.format("/images/{3}/cache/{0}x{1}/{2}.{4}",
                width, height, imageId, entityName, type).replace("/", System.getProperty("file.separator"));
        File cacheDirectory = new File(MessageFormat.format("{0}{1}", applicationPath,
                MessageFormat.format("/images/{2}/cache/{0}x{1}", width, height, entityName).replace("/", System.getProperty("file.separator"))));
        if (!cacheDirectory.exists()) {
            cacheDirectory.mkdirs();
        }
        String fullCachePath = MessageFormat.format("{0}{1}", applicationPath, relativePath);
        return new File(fullCachePath);
    }

    public static String saveImage(String dataPath, String entityName, String format, BufferedImage pictureData, String imageId) throws IOException {
        String relativeFilePath = String.format("%1$s%3$s%1$s%4$s.%2$s", System.getProperty("file.separator"), format, entityName, imageId);
        String fullFilePath = String.format("%1$s%2$s", dataPath, relativeFilePath);
        try {
            ImageIO.write(pictureData, "jpeg", new File(fullFilePath));
        } catch (NullPointerException ex) {
            ex.printStackTrace();
            throw ex;
        }
        return relativeFilePath;
    }


    public static InputStream getDefaultBrandImage(String applicationPath, int width, int height, String type) throws IOException {
        BufferedImage image = getCachedImage(applicationPath, "/default/noimage.png", 1, width, height, "default", type, false);
        return convertToInputStream(type, image);
    }

    public static InputStream getDefaultProductImage(String dataPath, int width, int height, String type) throws IOException {
        BufferedImage image = getCachedImage(dataPath, "/default/noimage.png", 2, width, height, "default", type, false);
        return convertToInputStream(type, image);
    }

    private static InputStream convertToInputStream(String type, BufferedImage image) throws IOException {
        ByteArrayOutputStream imageStream = new ByteArrayOutputStream();
        ImageIO.write(image, type, imageStream);
        return new ByteArrayInputStream(imageStream.toByteArray());
    }

    public static String getMD5HashCode(BufferedImage pictureData) throws IOException {
        byte[] imageData = getByteArrayForPicture(pictureData);
        return DigestUtils.md5Hex(imageData);
    }

    private static byte[] getByteArrayForPicture(BufferedImage pictureData) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(pictureData, "jpeg", baos);
        byte[] bytesOut = baos.toByteArray();
        return bytesOut;
    }

    public static boolean isImageValid(PictureEntity picture, String dataPath) {
        File image = new File(getOSIndependentFilePath(MessageFormat.format("{0}{1}", dataPath, picture.filePath)));
        return image.canRead();
    }

    public static void clearProductImageCache(PictureEntity picture, Integer id, String dataPath) {
        String relativePath = "/images/products/cache";
        File cacheDirectory = new File(getOSIndependentFilePath(MessageFormat.format("{0}{1}", dataPath, relativePath)));
        File[] files = cacheDirectory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                ImagenameFilter filter = new ImagenameFilter(picture.id);
                File[] filesToClear = file.listFiles(filter);
                if (filesToClear != null)
                    for (int i = filesToClear.length - 1; i > -1; i--) {
                        filesToClear[i].delete();
                    }
            }
        }
    }


    public static InputStream getCrawlerImage(String dataPath, String imageName) throws IOException {
        String filePath = String.format("%1$s/crawlers/logo/%2$s", dataPath, imageName);
        BufferedImage imageFromDBPath = ImageIO.read(new File(filePath));
        return convertToInputStream("png", imageFromDBPath);
    }
    public static models.shop.Image saveImage(URL url, String dataPath, String folder) throws IOException {
        BufferedImage bufferedImage = ImageIO.read(url);
        return saveImage(bufferedImage, dataPath, folder);
    }
    public static models.shop.Image saveImage(File imageFile, String dataPath, String folder) throws IOException {
        BufferedImage bufferedImage = ImageIO.read(imageFile);
        return saveImage(bufferedImage, dataPath, folder);
    }
    
    public static models.shop.Image saveImage(BufferedImage image, String dataPath, String folder) throws IOException {
        String imageId = getMD5HashCode(image);
        String relativeFilePath = String.format("%1$s%2$s%1$s%3$s.png", System.getProperty("file.separator"), folder, imageId);
        String fullFilePath = String.format("%1$s%2$s", dataPath, relativeFilePath);
        try {
            File saveDirectory = new File(String.format("%1$s%2$s%3$s", dataPath, System.getProperty("file.separator"), folder));
            if (!saveDirectory.exists())
                saveDirectory.mkdirs();
            ImageIO.write(image, "png", new File(fullFilePath));
        } catch (NullPointerException ex) {
            ex.printStackTrace();
            throw ex;
        }
        return new models.shop.Image(relativeFilePath, imageId);
    }

    public static void deleteImage(models.shop.Image image, String dataPath) {
        if (image != null) {
            File duplicateImage = new File(String.format("%1$s%2$s", dataPath, getOSIndependentFilePath(image.filePath)));
            if (duplicateImage.exists()) {
                duplicateImage.delete();
            }
        }
    }
}
